Skip to content

Feat: Tambahkan unit & integration tests untuk lifecycle API key - #1684

Merged
analisopendesa merged 5 commits into
rilis-devfrom
feat/api-test-lifecycle
Jul 28, 2026
Merged

Feat: Tambahkan unit & integration tests untuk lifecycle API key#1684
analisopendesa merged 5 commits into
rilis-devfrom
feat/api-test-lifecycle

Conversation

@pandigresik

Copy link
Copy Markdown
Contributor

Feat: Tambahkan unit & integration tests untuk lifecycle API key (#1675)

Description

Menambahkan unit tests dan integration tests di OpenDK yang men-cover seluruh lifecycle API key: pembuatan, validasi, revocation/disable, scope enforcement, dan audit logging. Tests menggunakan SQLite in-memory sehingga dapat dijalankan tanpa dependensi MySQL, menjadikannya cocok untuk CI.

Changes made:

  1. Feature - Model: Menambahkan ApiKey model dengan status constants (active/revoked/disabled/expired), helper methods (isActive, isExpired, hasScope), dan key generation utilities.
  2. Feature - Model: Menambahkan ApiKeyAuditLog model untuk mencatat setiap operasi API key (created, validated, revoked, disabled).
  3. Feature - Service: Menambahkan KeyService yang mengelola lifecycle API key: create, validate (dengan scope check), revoke, disable, enable, dan audit logging.
  4. Feature - Controller: Menambahkan ApiKeyController dengan endpoint CRUD (index, store, show, revoke, destroy) yang menggunakan StoreApiKeyRequest untuk validasi.
  5. Feature - Middleware: Menambahkan ApiKeyMiddleware yang memvalidasi API key dari bearer token, mendukung optional scope parameter, dan mengembalikan status code yang sesuai (401/403).
  6. Feature - Form Request: Menambahkan StoreApiKeyRequest untuk validasi input pembuatan API key (name required, scopes optional array, expires_at optional date).
  7. Feature - Routes: Menambahkan route group api-keys dengan middleware auth:api (JWT auth tanpa token.registered), dan route key/validate dengan middleware api.key.
  8. Feature - Migration: Menambahkan migration api_keys dan api_key_audit_logs dengan foreign key ke users menggunakan unsignedInteger (compatibility dengan struktur users.id existing).
  9. Feature - Factory: Menambahkan ApiKeyFactory (dengan states: revoked, disabled, expired, withScope, withRawKey) dan SamplePayloadFactory (dengan state: penduduk).
  10. Feature - Tests: Menambahkan 18 integration tests (ApiKeyIntegrationTest) yang menguji controller + middleware + database menggunakan SQLite in-memory.
  11. Feature - Tests: Menambahkan 18 unit tests (KeyServiceTest) yang menguji KeyService langsung (create, validate, revoke, disable/enable, scope, audit log).
  12. Feature - Test Infrastructure: Menambahkan ApiKeyTestCase base class yang force SQLite in-memory sebelum app boot, dan WithApiKeyTesting trait yang membuat schema SQLite-compatible.
  13. Refactor - Config: Membersihkan phpunit.xml dari atribut deprecated (beStrictAboutTodoAnnotatedTests, verbose, processUncoveredFiles, crap4j, phpcov) untuk kompatibilitas dengan PHPUnit 12.
  14. Refactor - Config: Meregister middleware api.key di bootstrap/app.php.

Reason for change:

  • Point 1: API key functionality belum memiliki test coverage, sehingga perubahan pada service/middleware dapat mengakibatkan regression yang tidak terdeteksi.
  • Point 2: Tests yang ada di project menggunakan MySQL, sehingga membutuhkan database server untuk menjalankannya. SQLite in-memory memungkinkan tests berjalan tanpa dependensi external.
  • Point 3: Negative test cases (missing key, invalid key, revoked key, insufficient scope) belum ter-cover, padahal ini adalah edge cases penting untuk keamanan API.

Impact of change:

Test Coverage: Module API key memiliki minimal 80% test coverage dengan 36 tests (101 assertions) yang menjalankan seluruh lifecycle.
CI Compatibility: Tests dapat dijalankan dengan php artisan test --testsuite=ApiKey tanpa MySQL, sehingga lebih cepat dan reliable di CI.
Code Quality: Penggunaan FormRequest untuk validasi, middleware untuk autentikasi, dan Service layer untuk business logic menjadikan kode lebih terstruktur dan maintainable.
Negative Cases: Edge cases seperti revoked key, disabled key, insufficient scope, dan idempotency sudah ter-cover dalam tests.

Related Issue

  • Solution for feature related to issue #1675

Steps to Reproduce

Before (no tests):

  1. Tidak ada tests untuk API key module
  2. Perubahan pada KeyService/ApiKeyMiddleware tidak terdeteksi oleh automated tests
  3. Tidak ada negative test cases untuk edge cases keamanan

After (with tests):

  1. Jalankan php artisan test --testsuite=ApiKey
  2. ✅ 36 tests passed (101 assertions)
  3. ✅ Seluruh lifecycle ter-cover: create → validate → revoke/disable → audit log
  4. ✅ Negative cases ter-cover: missing key, invalid key, revoked key, insufficient scope, idempotency

Testing on related features:

  • KeyService create/validate/revoke ✅ Passed
  • ApiKeyMiddleware auth/scope handling ✅ Passed
  • ApiKeyController CRUD operations ✅ Passed
  • Audit logging per request ✅ Passed

Checklist

  • Unit tests (Pest) untuk KeyService (create, revoke, validate) - 18 tests
  • Integration tests untuk controller + middleware + database (SQLite in-memory) - 18 tests
  • Factories untuk test fixtures (ApiKeyFactory, SamplePayloadFactory)
  • Negative tests: missing key, invalid key, revoked key, insufficient scope
  • Idempotency/duplicate submission test
  • Test teardown/DB rollback via transaction
  • Tests run reliably in CI (SQLite in-memory)
  • phpunit.xml cleaned from deprecated attributes for PHPUnit 12

Technical Details

Technical Explanation

API Key Lifecycle Flow:

User (JWT) → ApiKeyController → KeyService::create() → DB (api_keys)
                                                          ↓
                                                     Audit Log (created)

External Client → ApiKeyMiddleware → KeyService::validate() → Scope Check → DB (last_used_at)
                                                                              ↓
                                                                         Audit Log (validate.success)

User (JWT) → ApiKeyController → KeyService::revoke() → DB (status=revoked)
                                                          ↓
                                                     Audit Log (revoked)

Test Architecture:

  • ApiKeyTestCase extends BaseTestCase dengan CreatesApplication, WithApiKeyTesting, WithSettingAplikasi
  • setUpBeforeClass() force $_ENV['DB_CONNECTION'] = 'sqlite' sebelum app boot untuk override .env.testing yang menggunakan MySQL
  • WithApiKeyTesting trait membuat schema SQLite-compatible (tanpa ALTER TABLE CHANGE yang tidak didukung SQLite)
  • Setiap test dijalankan dalam transaction yang di-rollback di tearDown()

Key Design Decisions:

  • users.id di project ini menggunakan int unsigned (32-bit), sehingga FK ke users harus menggunakan unsignedInteger bukan foreignId (yang default unsignedBigInteger)
  • Route api-keys hanya membutuhkan auth:api middleware (tanpa token.registered) karena management API keys adalah operasi autentikasi itu sendiri
  • KeyService::validate() menggunakan Hash::check() untuk membandingkan raw key dengan hashed key di database, sehingga raw key tidak pernah disimpan

Configuration changes

  • phpunit.xml: Hapus atribut deprecated (beStrictAboutTodoAnnotatedTests, verbose, processUncoveredFiles, crap4j, phpcov) dan tambahkan testsuite ApiKey
  • bootstrap/app.php: Register middleware api.keyApiKeyMiddleware::class
  • routes/api.php: Tambahkan route group api-keys (dengan auth:api) dan key/validate (dengan api.key)

Dependencies added

No new dependencies.

Testing

Automated Testing

  • Unit Test - KeyServiceTest: 18 tests (create, validate, revoke, disable/enable, scope, audit log, idempotency, expired key)
  • Integration Test - ApiKeyIntegrationTest: 18 tests (controller CRUD, middleware auth/scope, negative cases, audit log)
  • Total: 36 tests, 101 assertions

Manual Testing

  • Jalankan php artisan migrate untuk membuat tabel api_keys dan api_key_audit_logs
  • Login ke aplikasi dan buat API key melalui API endpoint
  • Gunakan API key yang dibuat untuk mengakses endpoint yang dilindungi middleware api.key
  • Coba akses dengan revoked key (harus 403)
  • Coba akses tanpa key (harus 401)
  • Coba akses dengan scope yang tidak mencukupi (harus 403)

Breaking Changes

None

Migration Guide

Jalankan php artisan migrate untuk membuat tabel baru:

  • api_keys: Menyimpan data API key (hashed) milik setiap user
  • api_key_audit_logs: Mencatat setiap operasi API key (create, validate, revoke, disable)

References


Additional notes:

  • Tests menggunakan SQLite in-memory sehingga tidak membutuhkan MySQL server
  • SamplePayloadFactory disediakan untuk test data payload API, dapat digunakan untuk integration testing lebih lanjut
  • WithApiKeyTesting trait dapat direuse untuk test lain yang membutuhkan schema users/das_setting di SQLite

@pandigresik
pandigresik requested a review from vickyrolanda July 21, 2026 02:10
@github-actions

Copy link
Copy Markdown
Contributor

🔄 AI PR Review sedang antri di server...

Proses review akan segera dimulai di background — hasil akan muncul sebagai komentar setelah selesai.
Powered by CrewAI · PR #1684

@analisopendesa

Copy link
Copy Markdown
Contributor
image ---

1. Prasyarat Lokal

  • Environment PHP: Digunakan PHP 8.4.22 (sesuai persyaratan dependensi proyek >= 8.4.0).
  • Ekstensi PHP: Ekstensi pdo_sqlite, openssl, dan sodium telah dipastikan aktif pada konfigurasi php.ini.
  • Dependensi Composer: composer install telah dijalankan dan semua paket berhasil terverifikasi/terinstal.
  • Konfigurasi Database SQLite: Pengujian memanfaatkan SQLite in-memory (DB_CONNECTION=sqlite, DB_DATABASE=:memory:) yang dikonfigurasi untuk dipaksa aktif sebelum aplikasi booting melalui ApiKeyTestCase.php.

2. Jalankan Migrasi & Testes (Unit & Integration)

Dilakukan pengujian seluruh testcase pada testsuite ApiKey dengan perintah:

php artisan test --testsuite=ApiKey

Hasil Keluaran:

   PASS  Tests\ApiKey\ApiKeyIntegrationTest
  ✓ can create api key via controller
  ✓ can list api keys
  ✓ can show api key detail
  ✓ can revoke api key via controller
  ✓ can delete api key via controller
  ✓ cannot access other users api key
  ✓ cannot revoke other users api key
  ✓ middleware rejects missing api key with 401
  ✓ middleware rejects invalid api key with 401
  ✓ middleware accepts valid api key
  ✓ middleware rejects revoked api key with 403
  ✓ middleware rejects disabled api key with 403
  ✓ middleware rejects insufficient scope with 403
  ✓ middleware accepts valid api key with correct scope
  ✓ idempotency - duplicate api key creation returns different keys
  ✓ create api key validates required fields
  ✓ create api key validates scopes must be array
  ✓ audit log records api key usage through middleware

   PASS  Tests\ApiKey\KeyServiceTest
  ✓ can create an api key
  ✓ can validate a valid api key
  ✓ returns invalid for non-existent key
  ✓ can revoke an api key
  ✓ can disable and enable an api key
  ✓ revoked key validation fails
  ✓ disabled key validation fails
  ✓ validation fails for insufficient scope
  ✓ validation passes with correct scope
  ✓ unscoped key passes any scope check
  ✓ audit log created on key creation
  ✓ audit log created on key validation
  ✓ audit log created on key revocation
  ✓ revoke returns null for non-existent key
  ✓ audit log tracks failed validation attempts
  ✓ idempotency - same user can create multiple keys with same name
  ✓ expired key validation returns expired status
  ✓ last_used_at is updated on successful validation

  Tests:    36 passed (101 assertions)
  Duration: 6.99s

Status: LULUS (36/36 passed)


3. Menjalankan Integration (Manual API Flow & Verifikasi Database)

Dilakukan simulasi alur API secara end-to-end terhadap sistem, dengan hasil pengetesan sebagai berikut:

  1. Pembuatan API Key (POST /api/v1/api-keys)

    • Header: Authorization: Bearer <jwt>
    • Payload: name=test-key-read&scopes[]=read
    • Hasil: HTTP 201 Created. Response mengembalikan raw_key satu kali dengan awalan opendk_ (rawKey: opendk_rwddpwe6q...).
  2. Akses Endpoint Dilindungi (Authorization: Bearer <rawKey>)

    • /api/v1/key/validate (tanpa syarat scope): HTTP 200 OK ("API key is valid").
    • /api/v1/key/validate-scope (syarat scope read): HTTP 200 OK ("API key with scope is valid").
    • Uji Insufficient Scope (membuat API Key dengan scope write, lalu mengakses /api/v1/key/validate-scope yang butuh scope read): HTTP 403 Forbidden ({"message":"API key does not have the required scope","status":"insufficient_scope"}).
  3. Revoke API Key (POST /api/v1/api-keys/{id}/revoke)

    • Header: Authorization: Bearer <jwt>
    • Hasil: HTTP 200 OK ("API key revoked successfully").
  4. Akses Setelah Revoke

    • Subsekuen request menggunakan rawKey yang sama ke endpoint /api/v1/key/validate:
    • Hasil: HTTP 403 Forbidden ({"message":"API key is revoked","status":"revoked"}).
  5. Pemeriksaan & Audit Database

    • Tabel api_keys (SELECT * FROM api_keys WHERE id = 1):
      • TIDAK ADA kolom yang menyimpan rawKey asli.
      • Kolom key_prefix hanya menyimpan prefix awalan 12 karakter (opendk_rwddp...).
      • Kolom key hanya menyimpan fingerprint/hash bcrypt ($2y$12$a9qVFFqkmEVt...).
    • Tabel api_key_audit_logs (SELECT * FROM api_key_audit_logs WHERE api_key_id = 1):
      • Log aktivitas mencatat 4 riwayat kejadian secara konsisten:
        • action: created (success: true)
        • action: validate.success (success: true)
        • action: validate.success (success: true)
        • action: revoked (success: true)
      • TIDAK ADA pencatatan rawKey asli pada kolom apa pun, termasuk pada payload maupun detail log.

@analisopendesa
analisopendesa merged commit f101eaf into rilis-dev Jul 28, 2026
@analisopendesa
analisopendesa deleted the feat/api-test-lifecycle branch July 28, 2026 13:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants